use std::ptr;

// Assuming these are defined in another module or crate
extern "C" {
    fn alloc_test_get_header(ptr: *mut u8) -> *mut BlockHeader;
    fn alloc_test_overwrite(ptr: *mut u8, length: usize, pattern: u8);
    fn alloc_test_malloc(bytes: usize) -> *mut u8;
    fn alloc_test_free(ptr: *mut u8);
    fn alloc_test_realloc(ptr: *mut u8, bytes: usize) -> *mut u8;
    fn alloc_test_calloc(nmemb: usize, bytes: usize) -> *mut u8;
    fn alloc_test_strdup(string: &str) -> *mut u8;
    fn alloc_test_set_limit(alloc_count: isize);
    fn alloc_test_get_allocated() -> usize;
}

type UnitTestFunction = unsafe extern "C" fn();

unsafe fn test_malloc_free() {
    let block: *mut u8;
    let block2: *mut u8;
    let block3: *mut u8;
    let block4: *mut u8;
    let mut ptr: *mut u8;
    let mut i: usize;

    assert_eq!(alloc_test_get_allocated(), 0);

    block = alloc_test_malloc(1024);

    assert!(!block.is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    ptr = block;

    for i in 0..1024 {
        assert_ne!(ptr.add(i).read_volatile(), 0);
    }

    alloc_test_free(block);

    block2 = alloc_test_malloc(1024);
    block3 = alloc_test_malloc(1024);
    block4 = alloc_test_malloc(1024);

    assert!(!block2.is_null());
    assert!(!block3.is_null());
    assert!(!block4.is_null());

    alloc_test_free(block2);
    alloc_test_free(block3);
    alloc_test_free(block4);
}

unsafe fn test_realloc() {
    let mut block: *mut u8;

    assert_eq!(alloc_test_get_allocated(), 0);

    block = alloc_test_malloc(512);
    assert!(!block.is_null());
    assert_eq!(alloc_test_get_allocated(), 512);

    alloc_test_set_limit(1);

    block = alloc_test_realloc(block, 1024);
    assert!(!block.is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    assert!(alloc_test_realloc(block, 2048).is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    alloc_test_free(block);

    alloc_test_set_limit(1);

    block = alloc_test_realloc(ptr::null_mut(), 1024);
    assert!(!block.is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    assert!(alloc_test_realloc(ptr::null_mut(), 1024).is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    alloc_test_free(block);
}

unsafe fn test_calloc() {
    let block: *mut u8;
    let mut i: usize;

    assert_eq!(alloc_test_get_allocated(), 0);

    block = alloc_test_calloc(16, 64);

    assert_eq!(alloc_test_get_allocated(), 1024);
    assert!(!block.is_null());

    for i in 0..1024 {
        assert_eq!(ptr::read_volatile(block.add(i)), 0);
    }

    alloc_test_free(block);

    assert_eq!(alloc_test_get_allocated(), 0);

    alloc_test_set_limit(1);

    block = alloc_test_calloc(1024, 1);
    assert!(!block.is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    assert!(alloc_test_calloc(1024, 1).is_null());
    assert_eq!(alloc_test_get_allocated(), 1024);

    alloc_test_free(block);
}

unsafe fn test_strdup() {
    let str: *mut u8;

    assert_eq!(alloc_test_get_allocated(), 0);

    str = alloc_test_strdup("hello world");

    assert!(!str.is_null());
    assert_eq!(std::ffi::CStr::from_ptr(str).to_str().unwrap(), "hello world");

    assert_eq!(alloc_test_get_allocated(), 12);

    alloc_test_free(str);

    assert_eq!(alloc_test_get_allocated(), 0);

    alloc_test_set_limit(1);

    str = alloc_test_strdup("hello world");
    assert!(!str.is_null());
    assert_eq!(alloc_test_get_allocated(), 12);

    assert!(alloc_test_strdup("hello world").is_null());
    assert_eq!(alloc_test_get_allocated(), 12);

    alloc_test_free(str);
}

unsafe fn test_limits() {
    let block: *mut u8;

    block = alloc_test_malloc(2048);
    assert!(!block.is_null());
    alloc_test_free(block);

    alloc_test_set_limit(1);
    block = alloc_test_malloc(1024);
    assert!(!block.is_null());
    assert!(alloc_test_malloc(1024).is_null());
    alloc_test_free(block);

    alloc_test_set_limit(-1);
    block = alloc_test_malloc(1024);
    assert!(!block.is_null());
    alloc_test_free(block);
}

unsafe fn run_tests(tests: &[UnitTestFunction]) {
    for test in tests.iter() {
        if *test == ptr::null::<extern "C" fn>() {
            break;
        }
        (*test)();
    }
}

fn main() {
    let tests = [
        Some(test_malloc_free),
        Some(test_realloc),
        Some(test_calloc),
        Some(test_strdup),
        Some(test_limits),
        None,
    ];

    unsafe { run_tests(&tests.map(|t| t.unwrap_or(ptr::null::<extern "C" fn>())) }; // Convert Option to pointer
}
